/***************************************************************************
 *   Copyright (C) 2004 by Nilesh Bansal                                   *
 *   nilesh@iitb.ac.in                                                     *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#include <QProcess>
#include <QTimer>
#include <QRegExp>

#include <KLocale>
#include <KDebug>

#include "ftpmonitorloop.h"

//default state for err, chosed radomly
#define NO_ERROR 999
#define DAEMON_NOT_RUNNING 888

FTPMonitorLoop::FTPMonitorLoop()
:QObject()
{
	//we don't start nothing yet, so we din't report error yet:)
	mLastQProcessError = NO_ERROR;
	mLastFtpWhoExitValue = NO_ERROR;
	mTimer = new QTimer(this);
	connect( mTimer, SIGNAL(timeout()), this, SLOT(execWhoCommand()) );
	mProc = new QProcess(this);
	mProcBeforeGrep = new QProcess(this);
	connect( mProc, SIGNAL(finished(int,QProcess::ExitStatus)),	this, SLOT(readFromStdout(int,QProcess::ExitStatus)) );
	connect( mProc, SIGNAL(error(QProcess::ProcessError)),		this, SLOT(execError(QProcess::ProcessError)) );
	//we need grep for sftpd, so we need to redirect output from ps -ef to grep
	mProcBeforeGrep->setStandardOutputProcess(mProc);
}


FTPMonitorLoop::~FTPMonitorLoop()
{
	delete mTimer;
	delete mProc;
	delete mProcBeforeGrep;
}

void FTPMonitorLoop::start(int interval,bool notifyNewClient)
{
	mLastQProcessError = NO_ERROR;
	mLastFtpWhoExitValue = NO_ERROR;
	mAllowPopup=notifyNewClient;
	mTimer->start(interval*1000);
	mListOfClients.clear();
}

void FTPMonitorLoop::stop()
{
	mTimer->stop();
	mFtpWhoCommand.args.clear();
	mFtpWhoCommand.bin=QString();
}

QString FTPMonitorLoop::getExecutedFtpWho()
{
	//if we use sude on mFtpWhoCommand.args.at(1) position is ftpwho binary with/without path
	if (mFtpWhoCommand.bin=="sudo")
		return mFtpWhoCommand.args.at(1);
	else
		return mFtpWhoCommand.bin;

}

bool FTPMonitorLoop::ftpdServerIsRunning(QString ftpdServerName)
{
	QProcess checkFtpd;
	checkFtpd.start( "pidof " + ftpdServerName );
	
	if (!checkFtpd.waitForStarted())
		return true;
	
	if (!checkFtpd.waitForFinished())
		return true;
	
	if ( checkFtpd.exitCode() == 0 )
		return true;
	else
		return false;
}

bool FTPMonitorLoop::whichFtpwho(QString ftpdServerName)
{
	QProcess checkFtpd;
	//for vsftpd we use ps and it's always awailable
	if(ftpdServerName=="vsftpd")
		return true;
	
	checkFtpd.start("which " + getExecutedFtpWho());
	
	if (!checkFtpd.waitForStarted())
		return true;

	if (!checkFtpd.waitForFinished())
		return true;

	if ( checkFtpd.exitCode() == 0 )
		return true;
	else
		return false;
	
}

void FTPMonitorLoop::execWhoCommand()
{
	//in vsftpd case we use mProcBeforeGrep to execute who/ps command and mProc to exec grep
	//it's confusing but we don't need reconnect signals
	if (mFtpDaemon == vsftpd)
	{
		mProcBeforeGrep->start(mFtpWhoCommand.bin,mFtpWhoCommand.args);
		mProc->start("grep",QStringList("vsftpd"));
	}
	else
		mProc->start(mFtpWhoCommand.bin,mFtpWhoCommand.args);
}

void FTPMonitorLoop::execError ( QProcess::ProcessError err )
{
	QString msg;
	//reset mLastFtpWhoExitValue variable, it's used to save last err number
	//and if we go here last error was't about exitcode status so reset is needed
	if (mLastFtpWhoExitValue!=NO_ERROR) mLastFtpWhoExitValue=NO_ERROR;
	//if we hit the same error don't do notification about error state again
	if ( mLastQProcessError == (int) err )
		return;
	//if we hit new type of error number remember it for future
	else
		mLastQProcessError = (int) err;

	if ( whichFtpwho( ftpdServerName() ) == false )
	{
		emit sigErrorOccurs( err, QString("Error occurs (execError) : Binary <b>%1</b> is not accesible! "\
						  "(Check if is placed on $PATH?)").arg( getExecutedFtpWho() ) );
		return;
	}
	else if ( ftpdServerIsRunning( ftpdServerName() ) == false )
	{
		emit sigErrorOccurs( err, QString("Error occurs (execError) : %1 daemon is not running !").arg( ftpdServerName() ) );
		return;
	}
	
	//from qt doc:
	//QProcess::FailedToStart :	process failed to start. Either the invoked program is missing,
	//				or you may have insufficient permissions to invoke the program.
	msg = QString("Command: <b>\"%1\"</b> failed !\n").arg(mFtpWhoCommand.bin);
	if ( err == QProcess::FailedToStart )
		msg += QString("You don't have permission to exec it, or this command doesn't exist");
	else
		msg += QString("Unspecified error from \"%1\" occurs (errcode:%2))")
			.arg(mFtpWhoCommand.bin).arg(err);
	//if we hit error from qprocess notify user about this issue, we can't process ftpdaemon state
	emit sigErrorOccurs( mLastQProcessError, msg );
}

void FTPMonitorLoop::readFromStdout(int exitCode,QProcess::ExitStatus)
{
	QString errMsg=QString("Error occurs (readFromStdout) : <b>%1</b> daemon is not running !")
		 .arg( ftpdServerName() );
	//if we go here we have to reset mLastQProcessError which is processed in execError
	if (mLastQProcessError!=NO_ERROR) mLastQProcessError=NO_ERROR;
	//we process error states only when exitcode has been changed
	if ( exitCode != mLastFtpWhoExitValue && exitCode != 0 )
	{
		if ( whichFtpwho( ftpdServerName() ) == false )
		{
			//we could go here e.g. if we use sudo but we can't find executed binary (ftpwho)
			errMsg=QString(	"Error occurs (readFromStdout) : Binary <b>%1</b> is not accesible! " \
					"(Check if is placed on $PATH?)").arg( getExecutedFtpWho() );
		}
		//check if the problem is not running ftpd server
		else if ( ftpdServerIsRunning( ftpdServerName() ) == true )
		{
			//if server is running grab output from stdout
			errMsg=mProc->readAllStandardOutput();
			//if stdout is empty grab output from stderr
			if (errMsg.isEmpty())
				errMsg=mProc->readAllStandardError();
			errMsg="<b>" + errMsg + "</b>";
			errMsg.prepend(QString("Command <b>\"%1\"</b> return exits with value (%2), with following output :\n")
				.arg(mFtpWhoCommand.bin).arg(exitCode));
			//if error ocurs for sudo in this case problem could be not correctly filled sudoers
			if (mFtpWhoCommand.bin=="sudo")
			{
				//if we use sude on mFtpWhoCommand.args.at(1) position is ftpwho binary with/without path
				errMsg.append(QString("This issue occurs, probably because you don't have properly " \
				"filled <b>sudoers</b> file. You have to use rule :\n<b>\"username ALL=NOPASSWD: %1\"</b>")
				.arg(mFtpWhoCommand.args.at(1)));
			}
		}
		emit sigErrorOccurs( exitCode, errMsg );
	}
	else if (exitCode==0)
	{
		//if exitCode is fine parse and process ftpwho stdout
		processStdOut(mProc->readAllStandardOutput());
		//if we have 0 connected clients something could be wrong
		if (mListOfClients.count()==0)
		{
			//so check if server is running
			if ( ftpdServerIsRunning( ftpdServerName() ) == false )
			{
				//if not running daemon was reported, don't popup info again
				if (mLastFtpWhoExitValue!=DAEMON_NOT_RUNNING)
				{
					mLastFtpWhoExitValue=DAEMON_NOT_RUNNING;
					emit sigErrorOccurs( exitCode, errMsg );
				}
				//we are calling return because we last line of this whole fucntion sets mLastFtpWhoExitValue
				return;
			}
			//if previous state was not OK (0) then change trayicon back to state (ok)
			//normally whan clients are already connected signal is called from
			//searchForClientsStateChange but if we have 0 clients connected signal
			//is not triggered
			if (mLastFtpWhoExitValue != exitCode)
				emit sigClientsCountChanged(0);
		}
	}
	
	if (mLastFtpWhoExitValue != exitCode) mLastFtpWhoExitValue = exitCode;
}

/// /////////////////// checks & emits to view /////////////////////////////////////////
//
//
void FTPMonitorLoop::searchForClientsStateChange(QVector<QStringList> newListOfClients)
{
    bool pairFound;
	
	//if count of connected clients has changed do something with icon
	if ( newListOfClients.count() != mListOfClients.count() )
		emit sigClientsCountChanged(newListOfClients.count());

	//go trought the whole old list client
	for (int i=0; i < mListOfClients.size(); ++i){
		pairFound=false;
		//and check if client row is present in new list
		for (int j=0; j < newListOfClients.size(); ++j){
			//to check use first colmn (position 0) which is PID number which is differ for each client
			if (mListOfClients.at(i).at(0) == newListOfClients.at(j).at(0)){
				//if we found old client PID is present in new client list
				//check if other columns didn't changed
				if (ftpClientDiff(mListOfClients.at(i),newListOfClients.at(j))==true){
					mListOfClients.replace(i,newListOfClients.at(j));
					emit sigChangeRow(newListOfClients.at(j),i);
				}
				//if we found old client PID is present in new client list, remove it from
				//temporary new client list for efficiency
				newListOfClients.remove(j);
				if (newListOfClients.size()>0) j--;
				pairFound=true;
				break;
			}
		}
		//if is'not old line present in new list remove it (client is disconnected)
		if (pairFound == false){
			mListOfClients.remove(i);
			emit sigDelRow(i);
			if (mListOfClients.size()>0) i--;
		}
	}
	
	//rest clients in new temporary list are newly connected clients so lets add them to the mListOfClients
	if (newListOfClients.size() > 0 ){
		for (int j=0; j < newListOfClients.size(); ++j){
			mListOfClients.push_back(newListOfClients.at(j));
			emit sigAddRow(newListOfClients.at(j));
		}

		//we can't bind sigClientsCountChanged to information about new clients e.g. 2 clients are new 2 were gone
		//ClientsCount was not changed but we have 2 new clients -- so emit info about new clients here ...
		//call sigNewClientsConnected to show popup window if allowed in config
		if (mAllowPopup){
			emit sigNewClientsConnected(newListOfClients.size());
		}
	}
}
